#include "cacheInfo.hpp"
#include "runCommand.hpp"

#include <regex>
#include <limits>

/* sysconf is only available on POSIX machines */
#if defined (HMS_IS_POSIX)
#include <unistd.h>
#endif

#ifdef _OPENMP
#include <omp.h>
#endif


namespace hms
{

std::string cacheInfo(){
	return runCommand("lscpu | grep \'cache\'");
}

long lastLevelCacheSize(){
	auto llSize { std::numeric_limits<long>::min() };
	/* this option is a whole lot easier, but it's not POSIX conforming */
	#if \
		   defined(_SC_LEVEL2_CACHE_SIZE) \
		&& defined(_SC_LEVEL3_CACHE_SIZE) \
		&& defined(_SC_LEVEL4_CACHE_SIZE)
		llSize = std::max( {
			sysconf(_SC_LEVEL2_CACHE_SIZE),
			sysconf(_SC_LEVEL3_CACHE_SIZE),
			sysconf(_SC_LEVEL4_CACHE_SIZE)
		} );
		if (llSize > 0)
			return llSize;
	#endif
	/* if the sysconf call is not available, call cacheInfo, read out the
	 * sizes and return the largest one */

	/* the pattern currently expects the cache sizes in kilobyte or megabyte.
	 * That should be fine for the foreseeable future. */
	std::regex pattern { ".+cache.+\\s+([0-9]+)\\s+(K|M).+" };

	auto err = [&](){
		throw std::runtime_error("Could not read last level cache size!");
	};
	
	std::stringstream cacheInfo { hms::cacheInfo() };
	if ( cacheInfo.str().empty() )
		err();

	/* cacheInfo() returns multiple lines, with each one containing a 
	 * cache level size, so we iterate over the lines and find the largest
	 * one. */
	std::string line;
	std::smatch match_out;
	
	while ( std::getline(cacheInfo, line) ){
		if ( std::regex_match( line, match_out, pattern ) ){
			std::ssub_match
				sizeString { match_out[1] },
				sizePrefix { match_out[2] };
			std::stringstream conv { sizeString.str() };
			long
				cacheSize,
				mult { 1024 * (sizePrefix.str() == "K" ? 1 : 1024) };
			if ( conv >> cacheSize )
				llSize = std::max(llSize, cacheSize * mult);
		}
	}

	if ( llSize == std::numeric_limits<long>::min() || llSize <= 0 )
		err();

	return llSize;
}

long cachePerThread(){
	int np {1};
	#ifdef _OPENMP
	np = omp_get_max_threads();
	#endif
	return lastLevelCacheSize() / np;
}

} // namespace hms
